﻿using System;
using System.Diagnostics;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Direct3D10;
using SharpDX.DXGI;
using SharpDX.Windows;
using SharpDX.RawInput;

using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using Device1 = SharpDX.Direct3D10.Device1;
using DriverType = SharpDX.Direct3D10.DriverType;
using Factory = SharpDX.DXGI.Factory;
using FeatureLevel = SharpDX.Direct3D10.FeatureLevel;
using System.Collections.Generic;
using SharpDX.Multimedia;
using ModelCreator;

namespace SpaceBattle
{
    public class D2DApp
    {
        public D2DApp(String windowTitle)
        {
            this.windowTitle = windowTitle;
        }

        // Window parameters
        private String windowTitle = "Model Creator";
        private RenderForm form = null;

        // Random generator
        Random rng = new Random();

        // Hardware resources
        SharpDX.Direct3D10.Device1 D3Device = null;
        SharpDX.DXGI.SwapChain DXGISwapChain = null;
        SharpDX.Direct2D1.Factory D2Factory = null;
        SharpDX.DirectWrite.Factory DWFactory = null;
        SharpDX.DXGI.Factory DXGIFactory = null;
        RenderTargetView renderView = null;
        RenderTarget d2dRenderTarget = null;
        Texture2D backBuffer = null;

        MouseInput mouse = null;
        KeyboardInput keyboard = new KeyboardInput();

        // Primitives
        List<Asteroid> asteroids = new List<Asteroid>();
        Text text = null;
        List<String> textLines = new List<String>();
        GeometryConstructor constructor = new GeometryConstructor();

        // Brushes
        SolidColorBrush solidColorBrush = null;

        public void Initialize()
        {
            form = new RenderForm(windowTitle);
            form.SetBounds(0, 0, 800, 600);
            form.Icon = new System.Drawing.Icon("../../images/application.ico");
            form.MouseClick += mouseClicked;
            form.MouseMove += mouseMoved;

            // SwapChain description
            var desc = new SwapChainDescription()
            {
                BufferCount = 2,
                ModeDescription =
                    new ModeDescription(form.ClientSize.Width, form.ClientSize.Height,
                                        new Rational(60, 1), Format.R8G8B8A8_UNorm),
                IsWindowed = true,
                OutputHandle = form.Handle,
                SampleDescription = new SampleDescription(1, 0),
                SwapEffect = SwapEffect.Discard,
                Usage = Usage.RenderTargetOutput
            };

            // Create Device and SwapChain
            Device1.CreateWithSwapChain(DriverType.Warp, DeviceCreationFlags.BgraSupport, desc,
                FeatureLevel.Level_10_0, out D3Device, out DXGISwapChain);

            D2Factory = new SharpDX.Direct2D1.Factory();
            DWFactory = new SharpDX.DirectWrite.Factory();

            // Ignore all windows events
            DXGIFactory = DXGISwapChain.GetParent<Factory>();
            DXGIFactory.MakeWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAll);

            // New RenderTargetView from the backbuffer
            backBuffer = Texture2D.FromSwapChain<Texture2D>(DXGISwapChain, 0);
            renderView = new RenderTargetView(D3Device, backBuffer);

            Surface surface = backBuffer.QueryInterface<Surface>();

            d2dRenderTarget = new RenderTarget(D2Factory, surface,
                new RenderTargetProperties(new PixelFormat(Format.Unknown, AlphaMode.Premultiplied)));
            d2dRenderTarget.AntialiasMode = AntialiasMode.PerPrimitive;

            solidColorBrush = new SolidColorBrush(d2dRenderTarget, Color.White);

            // Set input here
            var point = form.PointToClient(System.Windows.Forms.Control.MousePosition);            
            mouse = new MouseInput(form);
            SharpDX.RawInput.Device.RegisterDevice(UsagePage.Generic, UsageId.GenericMouse, DeviceFlags.None);
            SharpDX.RawInput.Device.MouseInput += mouse.Update;

            SharpDX.RawInput.Device.RegisterDevice(UsagePage.Generic, UsageId.GenericKeyboard, DeviceFlags.None);
            SharpDX.RawInput.Device.KeyboardInput += keyboard.Update;
            keyboard.KeyPressed += new KeyboardEventHandler(KeyPressed);

            // Create primitives here
            int width = form.ClientSize.Width;
            int height = form.ClientSize.Height;

            constructor.Initialization(d2dRenderTarget, D2Factory);

            text = new Text(d2dRenderTarget, DWFactory);

            for (int i = 0; i < 3; i++)
            {
                textLines.Add("");
            }
            textLines[0] = windowTitle;

            for (int i = 0; i < 300; i++)
            {
                asteroids.Add(new Asteroid(d2dRenderTarget, D2Factory, rng));
            }
        }

        private void mouseMoved(object sender, System.Windows.Forms.MouseEventArgs args)
        {
            constructor.SetFloatingPoint(mouse.GetLastMousePosition());
        }

        private void mouseClicked(object sender, System.Windows.Forms.MouseEventArgs args)
        {
            if (args.Button == System.Windows.Forms.MouseButtons.Left)
                constructor.SetPoint(mouse.GetLastMousePosition());
            if (args.Button == System.Windows.Forms.MouseButtons.Right)
                constructor.Interrupt();
        }

        public void KeyPressed(object sender, KeyboardInputEventArgs args)
        {
            if (args.Key == System.Windows.Forms.Keys.Escape)
            {
                form.Close();
            }
            text.AddContent("\n" + args.Key.ToString());
        }

        public void MouseEvent(object sender, MouseInputEventArgs args)
        {
            
        }

        public void Run()
        {
            // Game time watchers
            Stopwatch gameTime = new Stopwatch();
            gameTime.Start();

            Stopwatch tick = new Stopwatch();
            tick.Start();

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            // Main loop
            long previousTick = stopwatch.ElapsedMilliseconds;
            long previousTime = stopwatch.ElapsedMilliseconds;
            float dt = 0; // time to render single frame (in seconds)
            String textConstructor = "";

            RenderLoop.Run(form, () =>
            {
                // ================== Update timers =======================
                dt = (stopwatch.ElapsedMilliseconds - previousTick) * 0.001f;
                previousTick = stopwatch.ElapsedMilliseconds;

                // =================== Update scene ======================                
                // Process mouse input

                // Process keyboard input

                for (int i = 0; i < asteroids.Count; i++)
                {
                    asteroids[i].Update(dt);
                    if (asteroids[i].IsOutOfView())
                    {
                        asteroids[i] = new Asteroid(d2dRenderTarget, D2Factory, rng);
                    }
                }

                //Every second updates here
                if (stopwatch.ElapsedMilliseconds - previousTime >= 1000)
                {
                    // Update window title with new fps value
                    textLines[0] = windowTitle + " FPS: " + 1 / dt + " Last key: " + keyboard.GetLastKey().ToString();                    
                    previousTime = stopwatch.ElapsedMilliseconds;
                }
                
                // Every frame updates here
                textLines[1] = mouse.GetLastMousePosition().X + " " + mouse.GetLastMousePosition().Y;
                
                textConstructor = "";
                for (int i = 0; i < textLines.Count; i++)
                {
                    textConstructor += (textLines[i] + "\n");
                }
                text.SetContent(textConstructor);


                // ======================== Draw scene ========================
                d2dRenderTarget.BeginDraw();

                d2dRenderTarget.Clear(Color.Black);

                //solidColorBrush.Color = Color.Coral;
                //for (int i = 0; i < asteroids.Count; i++)
                //{
                //    asteroids[i].Draw(solidColorBrush);
                //}

                solidColorBrush.Color = Color.AliceBlue;
                constructor.Draw(solidColorBrush);

                solidColorBrush.Color = Color.LightSeaGreen;
                text.Draw(new DrawingPointF(0, 0), solidColorBrush);

                d2dRenderTarget.EndDraw();
                DXGISwapChain.Present(0, PresentFlags.None);
            });

            // Clear resources
            this.Clear();
        }

        private void Clear()
        {
            // Release all resources
            renderView.Dispose();
            backBuffer.Dispose();
            D3Device.ClearState();
            D3Device.Flush();
            D3Device.Dispose();
            D3Device.Dispose();
            DXGISwapChain.Dispose();
            DXGIFactory.Dispose();
        }
    }
}
